package com.teckteam.ptvsystem.model;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Observable;
import java.util.StringTokenizer;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import com.teckteam.ptvsystem.controller.PTVSiteSucksException;
import com.teckteam.ptvsystem.controller.ScrapeFailedException;
import com.teckteam.ptvsystem.model.facade.PTVConstants;
import com.teckteam.ptvsystem.model.facade.PTVSystem;
import com.teckteam.ptvsystem.view.GeocodePrompt;
import com.teckteam.ptvsystem.view.LoadingDialog;
import com.teckteam.ptvsystem.view.PTVUserInterface;

/**
 * Helper methods to organise collection of data
 * 
 * -James
 */
public class ScraperHelper extends Observable
{
   
   class GeocodeThread implements Runnable
   {
      ArrayList<String[]> gpsCoordList;
      String URL;
      
      public GeocodeThread(String placeBeingSearched)
      {
         URL = String.format(PTVConstants.GEOCODE_URL_KMLAPI, placeBeingSearched);
      }
      
      public void run()
      {
         Document doc = null;
         String arr[];
         
         Elements placeList;
         
         // Fetch page
         try {
            System.out.printf("DEBUG: %s\n", URL);
            doc = Jsoup.connect(URL).timeout(30000).get();
            // doc = Jsoup.parse(URL, 30000).get();
            System.err.println("Success load");
         } catch (IOException e) {
            // Do not proceed and exit function.
            System.err.println("Failed to load");
            ScraperHelper.this.notifyObservers(gpsCoordList);
            return;
         }
         
         // String debughtml = doc.html();
         gpsCoordList = new ArrayList<String[]>();
         System.out.println("Google is searching on Maps: "
               + doc.getElementsByTag("name").text());
         placeList = doc.getElementsByTag("Placemark");
         
         // System.out.println(placeList.select("address").get(0).text());
         
         // Process XML output for returning as object
         for (int i = 0; i < placeList.size(); i++) {
            Element place = placeList.get(i);
            
            // String arr[4] 0: rank 1: locality/suburb 2: latitude 3: longitude
            arr = new String[5];
            String tmp[] = place.select("coordinates").text().split(",");
            // key = place.select("ThoroughfareName").text();
            
            // if(place.select("AdministrativeAreaName").text().compareTo("VIC")!=0)
            // throw new Exception("Result contains non-victorian results");
            
            arr[0] = place.select("address").text();
            arr[1] = place.select("AddressDetails").attr("Accuracy");
            arr[2] = place.select("LocalityName").text();
            arr[3] = tmp[1];
            arr[4] = tmp[0];
            
            double lat = Double.parseDouble(tmp[1]), lon = Double
                  .parseDouble(tmp[0]);
            
            System.out.println(String.format(
                  "Key: %s\nRank %s, Locality:%s Lat:%f Lon:%f\n", arr[0], arr[1],
                  arr[2], lat, lon));
            
            // If accuracy is too low, throw error
//            if (Integer.parseInt(arr[1]) < PTVConstants.GEOCODE_RANK_THRESHOLD) {
//               System.err.println("Results are not concise enough");
//               gpsCoordList = null;
//               break;
//            }
            gpsCoordList.add(arr);
         }
         
         ScraperHelper.this.setChanged();
         ScraperHelper.this.notifyObservers(gpsCoordList);
      }
   }
   
   private GeocodeThread gt;
   private Thread thread;
   
   public ScraperHelper()
   {
      ;
   }
   
   public ScraperHelper(GeocodePrompt geocodePrompt)
   {
      addObserver(geocodePrompt);
   }
   
   public ScraperHelper(LoadingDialog ld)
   {
      addObserver(ld);
   }
   
   /**
    * Debug routine for printing out scraped timetable, rotated. (eg: rows
    * representing a single train's journey from start to finish)
    * 
    * @param timeTable
    */
   public void displayConsoleTimetableConsole(
         LinkedHashMap<String, String[]> timeTable)
   {
      ArrayList<String> nameDestination = getRouteStopNames(timeTable);
      // Test: display of contents in rotated order
      for (int i = 0; i < timeTable.get(nameDestination.get(0)).length; i++) {
         System.out.print("\nTransport Journey " + i + " ");
         for (int j = 0; j < nameDestination.size(); j++)
            System.out.print(timeTable.get(nameDestination.get(j))[i] + " ");
      }
   }
   
   /**
    * Redundant method to return scraped geocode details from last attempt
    * @return gt.gpsCoordList
    */
   public ArrayList<String[]> getGeocodeDetails()
   {
      if(gt==null) return null;
      
      return gt.gpsCoordList;
   }
   
   /**
    * Return a String ArrayList with all stop names of the scraped route
    * timetable information object.
    * 
    * @param timeTable
    * @return nameDestination
    */
   public ArrayList<String> getRouteStopNames(
         LinkedHashMap<String, String[]> timeTable)
   {
      ArrayList<String> nameDestination = new ArrayList<String>();
      Iterator<String> iterator = timeTable.keySet().iterator();
      
      while (iterator.hasNext()) {
         String s = iterator.next();
         if (s.compareTo("routeHeader") == 0 || s.compareTo("ampmCols") == 0)
            continue;
         
         nameDestination.add(s);
      }
      
      return nameDestination;
   }
   
   /**
    * Routine to scrape from the PTV/Metlink timetable website (client-side)
    * and collect all Timetable data into a LinkedHashMap object, containing String arrays
    * of departure times sorted into keys of their respective stop names.
    * 
    * Reserved keys: "ampmCols", "routeHeader"
    * 
    * routeHeader content structure:
    *    [0] = Route Name
    *    [1] = Timetable Day Flag
    *    [2] = Timetable Direction Flag
    *    [3] = Route ID Number (as per PTV website)
    *    [4] = Mode of transport Flag
    *   
    * 
    * @throws ScrapeFailedException
    * @throws PTVSiteSucksException 
    * @return timetableList
    */
   public LinkedHashMap<String, String[]> getTimetableDetails(String routeID, char directionFlag,
         String dayFlag, char transportTypeFlag) throws ScrapeFailedException, PTVSiteSucksException
   {
      String URL;
      ArrayList<String> nameDestination;
      LinkedHashMap<String, String[]> timetableList;
      Document doc = null;
      String rowTest, routeName;
      Element tableRow;
      Elements heading, minorStops;
      int i;
      
      URL = String.format(PTVConstants.TEMPLATE_URL, routeID, directionFlag,
            dayFlag);
      
      // Fetch page
      try 
      {
         doc = Jsoup.connect(URL).timeout(30000).get();
      } catch (IOException e) 
      {
         // Do not proceed and exit function.
         System.err.println("Failed to load");
         throw new ScrapeFailedException();
      }
      
      System.out.printf("%s [%s,%c]: \n", routeID, dayFlag, directionFlag);
      
      // String debughtml = doc.html();
      timetableList = new LinkedHashMap<String, String[]>();
      nameDestination = new ArrayList<String>();
      
      // Get name of route/line
      heading = doc.getElementsByTag("table").select("span");
      
      //If header is invalid, throw PTVSiteSucksError
      try
      {
         routeName = heading.get(0).text();   
      } catch (Exception e)
      {
         throw new PTVSiteSucksException(String.format("[%s,%s,%c]", routeID, dayFlag, directionFlag));
      }
      
      String routeHeader[] = new String[5];
      routeHeader[0] = routeName;
      routeHeader[1] = dayFlag;
      routeHeader[2] = Character.toString(directionFlag);
      routeHeader[3] = routeID;
      routeHeader[4] = Character.toString(transportTypeFlag); // Required for
                                                      // distinguishing routes
      timetableList.put("routeHeader", routeHeader);
      
      minorStops = doc.getElementsByClass("ttMarginNTP");
      // System.out.println(String.format("DEBUG: %s",
      // minorStops.select("div").text()));
      
      // Get station names
      for (i = 1;; i++) {
         try {
            rowTest = String.format("ttMR_row_%d", i);
            // tableRow = doc.getElementById(rowTest);
            tableRow = doc.select(String.format("div[id=%s]", rowTest)).first();
            String rowName = tableRow.text(),
                  
                   //NEEDED FOR GPS MERGE
                   stopCode = tableRow.select("a").attr("href").split("/")[5];
            
            if (rowName.substring(0, 2).matches("[^0-9A-Za-z\\-]*"))
               rowName = rowName.substring(2);
            
            //System.out.println(rowName);
            
            nameDestination.add(String.format("%s|%s", rowName, stopCode));
         } catch (Exception e) {
            // Break loop
            System.out.printf(" %d stations %s.\n", (i - 1), routeName);
            break;
         }
      }
      
      // Get times per station
      for (i = 1;; i++) {
         try {
            rowTest = String.format("ttBR_row_%d", i);
            tableRow = doc.getElementById(rowTest);
            // System.out.println(table.text());
            StringTokenizer st = new StringTokenizer(tableRow.text(), " ");
            ArrayList<String> timeElements = new ArrayList<String>();
            while (st.hasMoreTokens()) {
               timeElements.add(st.nextToken());
            }
            String timeArray[] = new String[timeElements.size()];
            timeArray = timeElements.toArray(timeArray);
            timetableList.put(nameDestination.get(i - 1), timeArray);
         } catch (Exception e) {
            // Break loop
            break;
         }
      }
      
      // Get am/pm headers
      heading = doc.getElementsByClass("ttHeader");
      ArrayList<String> headElem = new ArrayList<String>();
      StringTokenizer st = new StringTokenizer(heading.get(2).text(), " ");
      for (i = 0; st.hasMoreTokens(); i++)
         headElem.add(st.nextToken());
      
      String ampmCols[] = new String[headElem.size()];
      ampmCols = headElem.toArray(ampmCols);
      timetableList.put("ampmCols", ampmCols);
      
      // displayConsoleTimetableConsole(times);
      // displayTimetableWindow(times);
      
      // Return everything found and save to file
      PTVSystem.getInstance().saveTimetable(timetableList);
      return timetableList;
   }
   
   /**
    * Check if any scraper threads are currently running.
    * 
    * @return
    */
   public boolean isProcessing()
   {
      if (thread==null) return false;
      
      return thread.isAlive();
   }
   
   /**
    * Geocoding routines (using Google Maps API) Returns a LinkedHashMap object
    * containing a list of possible points within some predefined region.
    * Results that rank below the threshold will result into nothing being
    * returned
    * 
    * 
    * @return gpsCoordList
    * @throws Exception
    */
   public void launchGeocoder(String placeBeingSearched)
   {
      gt = new GeocodeThread(placeBeingSearched);
      thread = new Thread(gt);
      thread.start();
   }
   
   public LinkedHashMap<String, String[]> toStringArr(
         LinkedHashMap<String, AbstractStop> stopList)
   {
      // TODO transforms returned stop list back into its old format
      
      return null;
   }
}
